import { Toast } from "antd-mobile";
import EventEmitter from "eventemitter3";
import { BaseInfo, LogMessage, LogAll } from "../serve/interfaces";
import { currentDB } from "./index";
import { __CONFIG__, __PUBLIC_PATH__, doDecode } from "./common";
import { ERRORS } from "../serve/utils/common";
import { log_all } from "./apis";
const api_base = __PUBLIC_PATH__;

const EE = new EventEmitter();

export const handleMessage = async (_info: LogMessage & BaseInfo) => {
  const info = doDecode(_info)
  if (typeof info?._id === "string") {
    const localLog = await currentDB.log.get({ _id: info._id });
    if (localLog) {
      if (info.gmt_modified === localLog.gmt_modified) {
        return;
      }
      await currentDB.log.update(info._id as string, info);
    } else {
      await currentDB.log.add(info);
    }
  }

  EE.emit(info.type, info.message, info);
}

export const beginLoop = () => {
  let timer: NodeJS.Timer
  async function start_listen() {
    const old_last = await currentDB.log.orderBy("gmt_created").last();
    const incrementalUpdateCondition = old_last
      ? {
          gmt_modified: {
            $gt: old_last.gmt_created,
          },
        }
      : null;
    const res = await log_all(incrementalUpdateCondition)
    if (res.success) {
      const logs = res.data
      for (let i = 0; i < logs?.length; i++) {
        await handleMessage(logs[i])
      }
    }
    if (res.error === ERRORS.NOT_LOGIN) {
      location.reload()
    }
    timer = setTimeout(start_listen, __CONFIG__.loopTimeout || 500)
  }
  function visibilityChange() {
    if (document.hidden) {
      clearTimeout(timer as never)
    } else {
      start_listen()
      .catch(err => {
        Toast.show(err);
      });
    }
  };
  document.addEventListener("visibilitychange", visibilityChange, false);
  visibilityChange();
}

export const beginSSE = () => {
  let sse: EventSource
  async function onMessage(ev: MessageEvent<any>) {
    const str = ev.data;
    if (str === "1") {
      // 长连接心跳，忽略
      return;
    }
    const info: LogMessage & BaseInfo = JSON.parse(str);
    await handleMessage(info)
  }
  async function start_listen() {
    const _id = (await currentDB.log.orderBy("gmt_created").last())?._id;
    sse = new EventSource(`${api_base}/listen?_id=${_id || ""}`, { withCredentials: true });
    sse.addEventListener("message", onMessage);
    return new Promise((resolve, reject) => {
      sse.addEventListener('open', resolve)
      sse.addEventListener('error', reject)
    })
  }
  function visibilityChange() {
    if (document.hidden) {
      if (!!sse) {
        sse.removeEventListener('message', onMessage)
        sse.close();
      }
    } else {
      start_listen()
      .catch(err => {
        Toast.show(err);
      });
    }
  };
  document.addEventListener("visibilitychange", visibilityChange, false);
  visibilityChange();
};

export const beginWebSocket = () => {
  let ws: WebSocket
  async function onMessage(ev: MessageEvent<any>) {
    const str = ev.data;
    const info: LogMessage & BaseInfo = JSON.parse(str);
    await handleMessage(info)
  }
  async function start_listen() {
    const _id = (await currentDB.log.orderBy("gmt_created").last())?._id;
    ws = new WebSocket(new URL(`ws://${location.host}${api_base}/listen?_id=${_id || ""}`));
    ws.addEventListener("message", onMessage);
    return new Promise((resolve, reject) => {
      ws.addEventListener('open', resolve)
      ws.addEventListener('error', reject)
    })
  }
  function visibilityChange() {
    if (document.hidden) {
      if (!!ws) {
        ws.removeEventListener('message', onMessage)
        ws.close();
      }
    } else {
      start_listen()
      .catch(err => {
        Toast.show(err);
      });
    }
  };
  document.addEventListener("visibilitychange", visibilityChange, false);
  visibilityChange();
}

export const addEventListener = function <T extends keyof LogAll>(
  type: T,
  listener: {
    (data: LogAll[T], info?: LogMessage<T> & BaseInfo): void;
  }
) {
  EE.on(type, listener);
};

export const removeEventListener = function <T extends keyof LogAll>(
  type: T,
  listener: {
    (data: LogAll[T], info?: LogMessage<T> & BaseInfo): void;
  }
) {
  EE.off(type, listener);
};

export const addEventListeners = function <T extends keyof LogAll>(
  types: T[],
  listener: {
    (data: LogAll[T], info?: LogMessage<T> & BaseInfo): void;
  }
) {
  types.forEach(type => addEventListener(type, listener))
};

export const removeEventListeners = function <T extends keyof LogAll>(
  types: T[],
  listener: {
    (data: LogAll[T], info?: LogMessage<T> & BaseInfo): void;
  }
) {
  types.forEach(type => removeEventListener(type, listener))
};


addEventListener("server_error", (data) => {
  Toast.show({
    icon: 'fail',
    content: data.error,
    afterClose() {
      if (data.error === ERRORS.NOT_LOGIN) {
        location.reload()
      }
    },
  });
});

/* ---------------------- update to last_message alpha ---------------------- */
addEventListeners([
  'chatroom_create',
  'chatroom_delete',
  'chatroom_adduser',
  'chatroom_delete',
  'chatroom_update',
  'message_send',
  'message_cancel',
], (message, info) => {
  currentDB.chatroom_last_message.put(info)
})
/* ---------------------- update to last_message omega ---------------------- */
